/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023.
 * Description: support virtual address domain isolation
 * Author: zhuyan
 * Create: 2023-07-14
 */
#include <asm/pgtable-hwdef.h>
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/sched.h>
#include <linux/mm_types.h>
#include "internal/rtos_isolation_vm.h"

bool __ro_after_init isovm_enabled_entry;
__ro_after_init DEFINE_STATIC_KEY_FALSE(isovm_enabled);

static int __init setup_isovm_enable(char *str)
{
	/*
	 * Since static_branch_unlikely cannot be used in assembly code, if you jump to function
	 * processing, on the one hand, it will cause performance degradation, and on the other
	 * hand, the board cannot start after modification. Therefore, two variables isovm_enabled
	 * and isovm_enabled_entry are used to detect whether isovm is enabled. isovm_enabled_entry
	 * is used in the assembly code el0_sync function, and isovm_enabled is used elsewhere.
	 * Both variables must be assigned values at the same time.
	 */
	isovm_enabled_entry = true;
	static_branch_enable(&isovm_enabled);
	pr_info("rtos virtual address isolation enabled\n");
	return 1;
}
__setup("isovm", setup_isovm_enable);

asmlinkage long noinstr do_isovm_open(void)
{
	current->thread.t0sz = ISOVM_T0SZ_ISOLATION;
	sysreg_clear_set(tcr_el1, ISOVM_TCR_MASK, ISOVM_TCR_ISOLATION);
	sysreg_clear_set(ttbr1_el1, TTBR_SEC_MASK, 0);
	isb();
	return 0;
}

asmlinkage long noinstr do_isovm_close(void)
{
	current->thread.t0sz = ISOVM_T0SZ_NORMAL;
	sysreg_clear_set(tcr_el1, ISOVM_TCR_MASK, ISOVM_TCR_NORMAL);
	sysreg_clear_set(ttbr1_el1, TTBR_SEC_MASK, TTBR_SEC_MASK);
	isb();
	return 0;
}

asmlinkage long noinstr do_isovm_state(void)
{
	return (read_sysreg(tcr_el1) & TCR_T0SZ_MASK) == ISOVM_T0SZ_ISOLATION ? 1 : 0;
}

static bool isovm_check_invalid(unsigned long addr, unsigned long flag, unsigned int isovm_flag)
{
	// isovm is not enabled, but uses the isovm flag
	if ((!static_branch_unlikely(&isovm_enabled)) && (flag & isovm_flag)) {
		pr_err("isovm doesn't enabled, isovm_flag enabled\n");
		return true;
	}

	if (static_branch_unlikely(&isovm_enabled) && (!(flag & isovm_flag)) &&
	    (addr >= ISOVM_MAP_WINDOW_NORMAL)) {
		pr_err("specified address is out of range\n");
		return true;
	}

	// isovm is not enabled or isovm flag not set
	if ((!static_branch_unlikely(&isovm_enabled)) || (!(flag & isovm_flag)))
		return false;

	if (is_compat_task()) {
		pr_err("isovm doesn't support 32bit binary\n");
		return true;
	}

	if (addr != 0) {
		pr_err("isovm doesn't support specified address\n");
		return true;
	}

	return false;
}

bool isovm_check_mmap_invalid(unsigned long addr, unsigned long flags)
{
	return isovm_check_invalid(addr, flags, RTOS_MAP_ISOLATION_VM);
}

bool isovm_check_shm_invalid(unsigned long addr, unsigned long flags)
{
	return isovm_check_invalid(addr, flags, RTOS_SHM_ISOLATION_VM);
}

